package cn.zhentao.utils;

import com.arcsoft.face.*;
import com.arcsoft.face.enums.DetectMode;
import com.arcsoft.face.enums.DetectOrient;
import com.arcsoft.face.enums.ErrorInfo;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
 * 虹软人脸识别引擎工具类
 *
 * 封装虹软ArcFace SDK的核心功能，提供人脸识别相关的工具方法：
 * 1. 人脸检测 - 检测图片中的人脸位置和数量
 * 2. 人脸特征提取 - 提取人脸的特征向量用于比对
 * 3. 人脸比对 - 计算两个人脸特征的相似度
 * 4. 人脸属性分析 - 获取年龄、性别、3D角度等信息
 * 5. 活体检测 - 判断是否为真实人脸（防止照片攻击）
 *
 * 该工具类在Spring容器启动时自动初始化虹软引擎，
 * 加载必要的DLL文件并激活SDK授权
 *
 * @author 田国刚
 * @since 2023-04-16
 */
@Component
public class ArcFaceEngineUtil {

    /**
     * 虹软SDK应用ID，从配置文件中读取
     * 用于SDK激活和授权验证
     */
    @Value("${arcface.appId}")
    public String appId;

    /**
     * 虹软SDK密钥，从配置文件中读取
     * 用于SDK激活和授权验证
     */
    @Value("${arcface.sdkKey}")
    public String sdkKey;

    /**
     * 错误码，用于记录SDK操作的返回状态
     */
    int errorCode;

    /**
     * 虹软人脸识别引擎实例
     * 核心对象，用于执行所有人脸识别相关操作
     */
    private FaceEngine faceEngine;

    /**
     * 项目根路径，用于定位资源文件
     */
    String projectPath = System.getProperty("user.dir");

    /**
     * 初始化虹软人脸识别引擎
     *
     * 在Spring容器启动时自动执行，完成以下初始化步骤：
     * 1. 加载虹软SDK的DLL动态链接库
     * 2. 创建FaceEngine实例
     * 3. 激活SDK授权（在线激活）
     * 4. 初始化引擎功能（人脸检测、识别、年龄性别检测、活体检测等）
     *
     * 如果初始化失败，会打印错误信息但不会阻止应用启动
     */
    @PostConstruct
    public void init() {
        try {
            System.out.println("开始初始化虹软人脸识别引擎...");

            // 1. 按正确顺序加载虹软SDK的DLL动态链接库
            loadNativeLibrariesInOrder();

            System.out.println("DLL库加载完成，开始创建FaceEngine实例...");

            // 2. 创建虹软人脸识别引擎实例
            faceEngine = new FaceEngine();

            // 3. 在线激活虹软SDK
            // 使用配置文件中的appId和sdkKey进行在线激活
            errorCode = faceEngine.activeOnline(appId, sdkKey);
            if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
                System.out.println("引擎激活失败，错误码：" + errorCode);
                return;
            }

            // 4. 配置引擎检测参数
            EngineConfiguration engineConfiguration = new EngineConfiguration();
            engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE); // 图片检测模式
            engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT); // 检测所有角度的人脸
            engineConfiguration.setDetectFaceMaxNum(10); // 最多检测10个人脸
            engineConfiguration.setDetectFaceScaleVal(16); // 人脸尺寸阈值

            // 5. 配置引擎功能模块
            FunctionConfiguration functionConfiguration = new FunctionConfiguration();
            functionConfiguration.setSupportAge(true); // 启用年龄检测
            functionConfiguration.setSupportFace3dAngle(true); // 启用3D角度检测
            functionConfiguration.setSupportFaceDetect(true); // 启用人脸检测
            functionConfiguration.setSupportFaceRecognition(true); // 启用人脸识别
            functionConfiguration.setSupportGender(true); // 启用性别检测
            functionConfiguration.setSupportLiveness(true); // 启用活体检测（RGB）
            functionConfiguration.setSupportIRLiveness(true); // 启用红外活体检测
            engineConfiguration.setFunctionConfiguration(functionConfiguration);

            // 6. 初始化引擎
            errorCode = faceEngine.init(engineConfiguration);
            if (errorCode != ErrorInfo.MOK.getValue()) {
                System.out.println("初始化引擎失败，错误码：" + errorCode);
            } else {
                System.out.println("虹软人脸识别引擎初始化成功！");
            }
        } catch (Exception e) {
            System.out.println("虹软引擎初始化异常：" + e.getMessage());
            e.printStackTrace();
        }
    }
    /**
     * 加载虹软SDK的DLL文件（备用方法）
     *
     * 这是一个备用的DLL加载方法，通过临时文件的方式加载DLL
     * 当直接加载方式失败时可以使用此方法
     *
     * @throws Exception 当DLL文件不存在或加载失败时抛出异常
     */
    private void loadArcsoftDll() throws Exception {
        // 从classpath中获取DLL文件资源
        URL dllUrl = getClass().getClassLoader().getResource("libs/WIN64/libarcsoft_face.dll");
        if (dllUrl == null) {
            throw new IOException("未找到libarcsoft_face.dll文件，请检查资源路径！");
        }

        // 创建临时目录用于存放DLL文件
        File tempDir = new File(System.getProperty("java.io.tmpdir"), "arcsoft-dll");
        if (!tempDir.exists()) {
            tempDir.mkdirs();
        }

        // 将DLL文件复制到临时目录
        File tempDll = new File(tempDir, "libarcsoft_face.dll");
        Files.copy(dllUrl.openStream(), tempDll.toPath(), StandardCopyOption.REPLACE_EXISTING);

        // 将临时目录添加到系统库路径
        addLibraryPath(tempDir.getAbsolutePath());

        // 加载DLL库
        System.loadLibrary("libarcsoft_face");
        System.out.println("成功加载libarcsoft_face.dll，路径：" + tempDll.getAbsolutePath());
    }

    /**
     * 动态添加路径到java.library.path
     *
     * 使用反射机制动态修改JVM的库搜索路径
     * 这样可以在运行时添加DLL文件的搜索路径
     *
     * @param pathToAdd 要添加的路径
     * @throws Exception 当反射操作失败时抛出异常
     */
    private void addLibraryPath(String pathToAdd) throws Exception {
        // 通过反射获取ClassLoader的usr_paths字段
        final Field usrPathsField = ClassLoader.class.getDeclaredField("usr_paths");
        usrPathsField.setAccessible(true);

        // 获取当前的路径数组
        final String[] paths = (String[]) usrPathsField.get(null);

        // 检查路径是否已经存在，避免重复添加
        for (String path : paths) {
            if (path.equals(pathToAdd)) {
                return;
            }
        }

        // 创建新的路径数组，包含要添加的路径
        final String[] newPaths = java.util.Arrays.copyOf(paths, paths.length + 1);
        newPaths[newPaths.length - 1] = pathToAdd;
        usrPathsField.set(null, newPaths);
    }

    /**
     * 提取人脸特征
     *
     * 从图片文件中检测人脸并提取人脸特征向量
     * 特征向量用于后续的人脸比对和识别
     *
     * @param file 包含人脸的图片文件
     * @return FaceFeature 人脸特征对象，如果检测失败或无人脸则返回null
     */
    public FaceFeature getFaceFeature(File file) {
        // 1. 将图片文件转换为ImageInfo对象
        // ImageFactory.getRGBData()方法将图片转换为RGB格式的数据
        ImageInfo imageInfo;
        List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();

        try {
            imageInfo = ImageFactory.getRGBData(file);
            System.out.println("图片信息 - 宽度: " + imageInfo.getWidth() + ", 高度: " + imageInfo.getHeight() + ", 格式: " + imageInfo.getImageFormat());

            // 2. 执行人脸检测
            // 在图片中检测人脸位置和角度信息
            errorCode = faceEngine.detectFaces(
                imageInfo.getImageData(),    // 图片数据
                imageInfo.getWidth(),        // 图片宽度
                imageInfo.getHeight(),       // 图片高度
                imageInfo.getImageFormat(),  // 图片格式
                faceInfoList                 // 检测结果列表
            );
        } catch (Exception e) {
            System.out.println("人脸检测过程中发生异常：" + e.getMessage());
            e.printStackTrace();
            return null;
        }

        // 3. 检查人脸检测结果
        if (errorCode != ErrorInfo.MOK.getValue() || faceInfoList.isEmpty()) {
            System.out.println("人脸检测失败或未检测到人脸，错误码：" + errorCode);
            System.out.println("错误码含义：" + getErrorMessage(errorCode));
            System.out.println("检测到的人脸数量：" + faceInfoList.size());
            return null;
        }

        System.out.println("检测到人脸数量：" + faceInfoList.size());

        // 4. 提取第一个检测到的人脸特征
        // 创建FaceFeature对象用于存储特征数据
        FaceFeature faceFeature = new FaceFeature();
        try {
            // 调用SDK的特征提取方法
            errorCode = faceEngine.extractFaceFeature(
                imageInfo.getImageData(),    // 图片数据
                imageInfo.getWidth(),        // 图片宽度
                imageInfo.getHeight(),       // 图片高度
                imageInfo.getImageFormat(),  // 图片格式
                faceInfoList.get(0),        // 使用第一个检测到的人脸
                faceFeature                  // 输出的特征对象
            );

            if (errorCode != ErrorInfo.MOK.getValue()) {
                System.out.println("人脸特征提取失败，错误码：" + errorCode);
                return null;
            }
        } catch (Exception e) {
            System.out.println("人脸特征提取过程中发生异常：" + e.getMessage());
            return null;
        }
        return faceFeature;
    }

    /**
     * 人脸特征相似度比对
     *
     * 比较两个人脸特征的相似度，返回相似度分数
     * 分数范围通常在0-1之间，分数越高表示越相似
     *
     * @param targetFaceFeature0 目标人脸特征（待比对的人脸）
     * @param sourceFaceFeature0 源人脸特征（参考人脸）
     * @return FaceSimilar 相似度对象，包含相似度分数
     */
    public FaceSimilar compareFaceFeature(FaceFeature targetFaceFeature0, FaceFeature sourceFaceFeature0) {
        // 创建新的FaceFeature对象，避免修改原始数据
        FaceFeature targetFaceFeature = new FaceFeature();
        targetFaceFeature.setFeatureData(targetFaceFeature0.getFeatureData());

        FaceFeature sourceFaceFeature = new FaceFeature();
        sourceFaceFeature.setFeatureData(sourceFaceFeature0.getFeatureData());

        // 执行人脸特征比对
        FaceSimilar faceSimilar = new FaceSimilar();
        errorCode = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);

        if (errorCode != ErrorInfo.MOK.getValue()) {
            System.out.println("人脸特征比对失败，错误码：" + errorCode);
        }

        return faceSimilar;
    }

    /**
     * 获取人脸详细信息
     *
     * 分析图片中的人脸，提取详细的人脸属性信息，包括：
     * - 年龄估算
     * - 性别识别
     * - 3D人脸角度（俯仰角、偏航角、翻滚角）
     * - 活体检测（判断是否为真实人脸）
     *
     * @param file 包含人脸的图片文件
     * @return HashMap 包含人脸属性信息的Map对象
     *         - gender: 性别（0-男性，1-女性）
     *         - age: 年龄估算值
     *         - 3DAngle: 3D角度信息
     *         - liveness: 活体检测结果（0-非活体，1-活体）
     */
    public HashMap<String, Object> getUserInfo(File file) {
        // 1. 将图片转换为ImageInfo对象
        ImageInfo imageInfo = ImageFactory.getRGBData(file);

        // 2. 执行人脸检测
        List<FaceInfo> faceInfoList = new ArrayList<FaceInfo>();
        errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);

        if (errorCode != ErrorInfo.MOK.getValue() || faceInfoList.isEmpty()) {
            System.out.println("人脸检测失败或未检测到人脸，错误码：" + errorCode);
            return new HashMap<>();
        }

        // 3. 设置活体检测参数
        // 第一个参数：RGB活体阈值（0.5），第二个参数：红外活体阈值（0.7）
        errorCode = faceEngine.setLivenessParam(0.5f, 0.7f);

        // 4. 配置人脸属性检测功能
        FunctionConfiguration configuration = new FunctionConfiguration();
        configuration.setSupportAge(true);         // 启用年龄检测
        configuration.setSupportFace3dAngle(true); // 启用3D角度检测
        configuration.setSupportGender(true);      // 启用性别检测
        configuration.setSupportLiveness(true);    // 启用活体检测

        // 5. 执行人脸属性分析
        errorCode = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, configuration);

        if (errorCode != ErrorInfo.MOK.getValue()) {
            System.out.println("人脸属性分析失败，错误码：" + errorCode);
            return new HashMap<>();
        }

        HashMap<String, Object> map = new HashMap<>();

        // 6. 获取性别信息
        List<GenderInfo> genderInfoList = new ArrayList<GenderInfo>();
        errorCode = faceEngine.getGender(genderInfoList);
        if (errorCode == ErrorInfo.MOK.getValue() && !genderInfoList.isEmpty()) {
            int gender = genderInfoList.get(0).getGender();
            System.out.println("性别：" + (gender == 0 ? "男性" : "女性"));
            map.put("gender", gender);
        }

        // 7. 获取年龄信息
        List<AgeInfo> ageInfoList = new ArrayList<AgeInfo>();
        errorCode = faceEngine.getAge(ageInfoList);
        if (errorCode == ErrorInfo.MOK.getValue() && !ageInfoList.isEmpty()) {
            int age = ageInfoList.get(0).getAge();
            System.out.println("年龄：" + age);
            map.put("age", age);
        }
        // 8. 获取3D角度信息
        List<Face3DAngle> face3DAngleList = new ArrayList<Face3DAngle>();
        errorCode = faceEngine.getFace3DAngle(face3DAngleList);
        if (errorCode == ErrorInfo.MOK.getValue() && !face3DAngleList.isEmpty()) {
            Face3DAngle angle = face3DAngleList.get(0);
            System.out.println("3D角度 - 俯仰角：" + angle.getPitch() +
                             "，翻滚角：" + angle.getRoll() +
                             "，偏航角：" + angle.getYaw());
            map.put("3DAngle", angle);
        }

        // 9. 获取活体检测结果
        List<LivenessInfo> livenessInfoList = new ArrayList<LivenessInfo>();
        errorCode = faceEngine.getLiveness(livenessInfoList);
        if (errorCode == ErrorInfo.MOK.getValue() && !livenessInfoList.isEmpty()) {
            int liveness = livenessInfoList.get(0).getLiveness();
            System.out.println("活体检测：" + (liveness == 1 ? "真实人脸" : "非真实人脸"));
            map.put("liveness", liveness);
        }

        return map;
    }

    /**
     * 获取错误码对应的错误信息
     *
     * @param errorCode 错误码
     * @return 错误信息描述
     */
    private String getErrorMessage(int errorCode) {
        switch (errorCode) {
            case 0:
                return "成功";
            case 1:
                return "无效参数";
            case 2:
                return "内存不足";
            case 3:
                return "状态错误";
            case 4:
                return "用户取消操作";
            case 5:
                return "操作超时或图片中无人脸";
            case 6:
                return "缓冲区大小不足";
            case 7:
                return "函数未实现";
            case 8:
                return "质量检测失败";
            case 9:
                return "图像质量太差";
            case 10:
                return "人脸特征长度不匹配";
            case 11:
                return "人脸特征不匹配";
            case 12:
                return "SDK未激活";
            case 13:
                return "SDK已激活";
            case 14:
                return "SDK激活码无效";
            case 15:
                return "SDK激活码已过期";
            case 16:
                return "设备指纹不匹配";
            case 17:
                return "唯一标识不匹配";
            case 18:
                return "参数为空";
            case 19:
                return "参数过大";
            case 20:
                return "图片编码格式不支持";
            case 21:
                return "图片像素格式不支持";
            case 22:
                return "图片宽度不支持";
            case 23:
                return "图片高度不支持";
            case 24:
                return "图片步长不支持";
            case 25:
                return "图片数据无效";
            default:
                return "未知错误码: " + errorCode;
        }
    }

    /**
     * 按正确顺序加载本地库文件
     * 解决DLL依赖问题
     */
    private void loadNativeLibrariesInOrder() {
        try {
            System.out.println("开始加载虹软SDK本地库文件...");

            // 获取当前操作系统
            String osName = System.getProperty("os.name").toLowerCase();
            System.out.println("检测到操作系统: " + osName);

            if (osName.contains("windows")) {
                loadWindowsLibraries();
            } else if (osName.contains("linux")) {
                loadLinuxLibraries();
            } else {
                throw new UnsupportedOperationException("不支持的操作系统: " + osName);
            }

            System.out.println("所有本地库文件加载完成");

        } catch (Exception e) {
            System.err.println("加载本地库失败: " + e.getMessage());
            e.printStackTrace();
            throw new RuntimeException("无法加载虹软人脸识别SDK本地库", e);
        }
    }

    /**
     * 加载Windows平台的库文件
     */
    private void loadWindowsLibraries() {
        String libPath = "libs/WIN64/";

        // 方法1: 尝试按依赖顺序逐个加载
        try {
            System.out.println("方法1: 按依赖顺序加载库文件...");

            // 按依赖顺序加载
            loadLibraryFromClasspath(libPath, "libarcsoft_face.dll");
            loadLibraryFromClasspath(libPath, "libarcsoft_face_engine.dll");
            loadLibraryFromClasspath(libPath, "libarcsoft_face_engine_jni.dll");

            System.out.println("方法1成功: 所有库文件加载完成");
            return;

        } catch (Exception e) {
            System.err.println("方法1失败: " + e.getMessage());
        }

        // 方法2: 尝试使用系统库路径
        try {
            System.out.println("方法2: 尝试使用系统库路径...");

            // 设置库路径到系统属性
            String tempDir = copyLibrariesToTemp(libPath);
            String currentLibPath = System.getProperty("java.library.path");
            System.setProperty("java.library.path", tempDir + ";" + currentLibPath);

            // 重新加载库路径（通过反射）
            refreshLibraryPath();

            // 尝试加载
            System.loadLibrary("arcsoft_face_engine_jni");
            System.out.println("方法2成功: JNI库加载完成");
            return;

        } catch (Exception e) {
            System.err.println("方法2失败: " + e.getMessage());
        }

        // 方法3: 最后的尝试 - 直接从临时目录加载所有库
        try {
            System.out.println("方法3: 从临时目录直接加载所有库...");
            String tempDir = copyLibrariesToTemp(libPath);

            System.load(tempDir + "\\libarcsoft_face.dll");
            System.load(tempDir + "\\libarcsoft_face_engine.dll");
            System.load(tempDir + "\\libarcsoft_face_engine_jni.dll");

            System.out.println("方法3成功: 所有库文件加载完成");

        } catch (Exception e) {
            System.err.println("方法3失败: " + e.getMessage());
            throw new RuntimeException("所有加载方法都失败了", e);
        }
    }

    /**
     * 复制所有库文件到临时目录
     */
    private String copyLibrariesToTemp(String libPath) throws Exception {
        File tempDir = new File(System.getProperty("java.io.tmpdir"), "arcsoft_libs_" + System.currentTimeMillis());
        if (!tempDir.exists()) {
            tempDir.mkdirs();
        }

        String[] libraries = {
            "libarcsoft_face.dll",
            "libarcsoft_face_engine.dll",
            "libarcsoft_face_engine_jni.dll"
        };

        for (String libName : libraries) {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream(libPath + libName);
            if (inputStream == null) {
                throw new RuntimeException("找不到库文件: " + libPath + libName);
            }

            File tempLib = new File(tempDir, libName);
            try (java.io.FileOutputStream outputStream = new java.io.FileOutputStream(tempLib)) {
                byte[] buffer = new byte[8192];
                int length;
                while ((length = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, length);
                }
            }
            inputStream.close();
        }

        return tempDir.getAbsolutePath();
    }

    /**
     * 刷新库路径（通过反射）
     */
    private void refreshLibraryPath() {
        try {
            Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
            fieldSysPath.setAccessible(true);
            fieldSysPath.set(null, null);
        } catch (Exception e) {
            System.err.println("刷新库路径失败: " + e.getMessage());
        }
    }

    /**
     * 加载Linux平台的库文件
     */
    private void loadLinuxLibraries() {
        String[] libraries = {
            "libarcsoft_face.so",
            "libarcsoft_face_engine.so",
            "libarcsoft_face_engine_jni.so"
        };

        String libPath = "libs/LINUX64/";
        loadLibrariesFromClasspath(libPath, libraries);
    }

    /**
     * 从classpath加载多个库文件
     */
    private void loadLibrariesFromClasspath(String libPath, String[] libraries) {
        for (String libName : libraries) {
            loadLibraryFromClasspath(libPath, libName);
        }
    }

    /**
     * 从classpath加载单个库文件
     */
    private void loadLibraryFromClasspath(String libPath, String libName) {
        try {
            System.out.println("正在加载库文件: " + libName);

            // 从classpath获取库文件
            URL resourceUrl = getClass().getClassLoader().getResource(libPath + libName);
            if (resourceUrl == null) {
                System.err.println("警告: 找不到库文件: " + libPath + libName);
                return;
            }

            String fullPath = resourceUrl.getPath();
            fullPath = URLDecoder.decode(fullPath, StandardCharsets.UTF_8.name());

            // 如果是jar包中的文件，需要复制到临时目录
            if (fullPath.contains("!")) {
                loadLibraryFromJar(libPath, libName);
            } else {
                // 直接从文件系统加载
                System.out.println("从文件系统加载: " + fullPath);
                System.load(fullPath);
                System.out.println("成功加载库文件: " + libName);
            }

        } catch (Exception e) {
            System.err.println("加载库文件失败: " + libName + ", 错误: " + e.getMessage());
            e.printStackTrace();
            throw new RuntimeException("无法加载库文件: " + libName, e);
        }
    }

    /**
     * 从JAR包中加载库文件
     */
    private void loadLibraryFromJar(String libPath, String libName) {
        try {
            // 创建临时目录
            File tempDir = new File(System.getProperty("java.io.tmpdir"), "arcsoft_libs");
            if (!tempDir.exists()) {
                tempDir.mkdirs();
            }

            // 从JAR中复制库文件到临时目录
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream(libPath + libName);
            if (inputStream == null) {
                throw new RuntimeException("无法找到库文件: " + libPath + libName);
            }

            File tempLib = new File(tempDir, libName);
            try (java.io.FileOutputStream outputStream = new java.io.FileOutputStream(tempLib)) {
                byte[] buffer = new byte[8192];
                int length;
                while ((length = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, length);
                }
            }
            inputStream.close();

            // 加载临时文件
            System.out.println("从临时目录加载: " + tempLib.getAbsolutePath());
            System.load(tempLib.getAbsolutePath());
            System.out.println("成功加载库文件: " + libName);

        } catch (Exception e) {
            throw new RuntimeException("从JAR加载库文件失败: " + libName, e);
        }
    }
}
